#include <tuple>
#include <vector>
#include <fstream>
#include <algorithm>
#include <cmath>
 

// 定义 vec3 类型和重载向量计算
struct vec3 {
    float x=0, y=0, z=0;
          float& operator[](const int i)       { return i==0 ? x : (1==i ? y : z); }
    const float& operator[](const int i) const { return i==0 ? x : (1==i ? y : z); }
    vec3  operator*(const float v) const { return {x*v, y*v, z*v};       }
    float operator*(const vec3& v) const { return x*v.x + y*v.y + z*v.z; }
    vec3  operator+(const vec3& v) const { return {x+v.x, y+v.y, z+v.z}; }
    vec3  operator-(const vec3& v) const { return {x-v.x, y-v.y, z-v.z}; }
    vec3  operator-()              const { return {-x, -y, -z};          }
    float norm() const { return std::sqrt(x*x+y*y+z*z); }
    vec3 normalized() const { return (*this)*(1.f/norm()); }
}; 


// 三维向量的叉乘 ,计算法线向量 ,垂直于两个向量的平面 
vec3 cross(const vec3 v1, const vec3 v2) {
    return { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x };
}

// 定义材质结构体
struct Material {
    float refractive_index = 1; // 折射率
    float albedo[4] = {2,0,0,0}; // 漫反射、镜面反射、反射、折射系数 
    vec3 diffuse_color = {0,0,0}; // 基础颜色
    float specular_exponent = 0; // 高光强度
};

// 定义球体结构体
struct Sphere {
    vec3 center;   // 球心
    float radius;  // 半径
    Material material; // 材质
};

// 定义常量材质
constexpr Material      ivory = {1.0, {0.9,  0.5, 0.1, 0.0}, {0.4, 0.4, 0.3},   50.}; // 铁
constexpr Material      glass = {1.5, {0.0,  0.9, 0.1, 0.8}, {0.6, 0.7, 0.8},  125.}; // 玻璃
constexpr Material red_rubber = {1.0, {1.4,  0.3, 0.0, 0.0}, {0.3, 0.1, 0.1},   10.}; // 红橡胶
constexpr Material     mirror = {1.0, {0.0, 16.0, 0.8, 0.0}, {1.0, 1.0, 1.0}, 1425.}; // 镜面

// 定义场景中的球体, 四个类型球体的数组
constexpr Sphere spheres[] = {
    {{-3,    0,   -16}, 2,      ivory},
    {{-1.0, -1.5, -12}, 2,      glass},
    {{ 1.5, -0.5, -18}, 3, red_rubber},
    {{ 7,    5,   -18}, 4,     mirror}
};

// 定义光源 
constexpr vec3 lights[] = {
    {-20, 20,  20}, // 左上  从左上侧照射，产生经典的主光源效果，能清晰展现物体右侧的阴影
    { 30, 50, -25}, // 右下 低角度强光源（y=50较高），用于补充暗部细节，同时通过负z值创造前后层次感
    { 30, 20,  30}  // 右上   平衡左上光源，防止单侧光照导致的过度明暗对比。
};

// 计算反射光线
vec3 reflect(const vec3 &I, const vec3 &N) {
    return I - N*2.f*(I*N); 
}

// 计算折射光线      
vec3 refract(const vec3 &I, const vec3 &N, const float eta_t, const float eta_i=1.f) { // Snell's law
    float cosi = - std::max(-1.f, std::min(1.f, I*N)); // 入射角余弦
    if (cosi<0) return refract(I, -N, eta_i, eta_t); //
    // 当cosi<0表示光线来自物体内部（法线方向相反）
    // 递归调用自身，交换法线方向和介质折射率
    //  if the ray comes from the inside the object, swap the air and the media
    float eta = eta_i / eta_t;   // 计算折射率
    float k = 1 - eta*eta*(1 - cosi*cosi); // 全反射条件
    
    return k<0 ? vec3{1,0,0} : I*eta + N*(eta*cosi - std::sqrt(k)); // k<0 = total reflection, no ray to refract. I refract it anyways, this has no physical meaning
    //  全反射时返回固定向量(1,0,0)（作者注：这没有物理意义，只是占位）, 光密到光疏 
}

 // 计算球体与光线的交点
std::tuple<bool,float> ray_sphere_intersect(const vec3 &orig, const vec3 &dir, const Sphere &s) { // ret value is a pair [intersection found, distance]
    vec3 L = s.center - orig; // 光线起点指向球心的向量
    float tca = L*dir;  // 向量L在光线方向dir上的投影长度（点积运算） 
    float d2 = L*L - tca*tca; // 根据勾股定理，d2就是球心到光线"直线"的垂直距离的平方 
    if (d2 > s.radius*s.radius) return {false, 0}; // 光线不与球体相交 
    float thc = std::sqrt(s.radius*s.radius - d2); //   thc是光线进入球体后的"弦长"的一半  
    float t0 = tca-thc, t1 = tca+thc; // 两个交点的距离   
    if (t0>.001) return {true, t0};  // offset the original point by .001 to avoid occlusion by the object itself
    if (t1>.001) return {true, t1}; // 优先选择t0（第一个交点），但要求距离大于0.001（避免自遮挡 
    return {false, 0};
}

// 计算场景中与光线相交的物体
std::tuple<bool,vec3,vec3,Material> scene_intersect(const vec3 &orig, const vec3 &dir) {
    vec3 pt, N;
    Material material;

    float nearest_dist = 1e10; // 最近交点距离初始化为无穷大 
    if (std::abs(dir.y)>.001) { //  避免除零错误  
        float d = -(orig.y+4)/dir.y; //  计算光线与水平面交点距离 
        vec3 p = orig + dir*d; //  计算交点 
        if (d>.001 && d<nearest_dist && std::abs(p.x)<10 && p.z<-10 && p.z>-30) { //  交点在水平面范围内，且距离最近 
            nearest_dist = d;  // 更新最近交点距离 
            pt = p;  // 记录交点位置   
            N = {0,1,0};  // 棋盘法线向上 
            material.diffuse_color = (int(.5*pt.x+1000) + int(.5*pt.z)) & 1 ? vec3{.3, .3, .3} : vec3{.3, .2, .1}; 
            //通过交点的x,z坐标计算棋盘格颜色 
        }
    }

    for (const Sphere &s : spheres) { // intersect the ray with all spheres
        auto [intersection, d] = ray_sphere_intersect(orig, dir, s);
        if (!intersection || d > nearest_dist) continue;
        nearest_dist = d;  // 更新最近交点距离 , 只需要物体表面的光线
        pt = orig + dir*nearest_dist;  // 记录交点位置   
        N = (pt - s.center).normalized(); // 记录法线向量 
        material = s.material;  // 记录材质信息  
    } 
    /// 1000是 一个经验值, 也就是假如将坐标映射到现实世界, 超过一千米的光线反射很微弱, 人眼自动忽略,所有设置的渲染阈值
    return { nearest_dist<1000, pt, N, material };
}

// 计算光线的颜色
vec3 cast_ray(const vec3 &orig, const vec3 &dir, const int depth=0) {
    auto [hit, point, N, material] = scene_intersect(orig, dir);
    if (depth>4 || !hit)
        return {0.2, 0.7, 0.8}; // background color 
 
    vec3 reflect_dir = reflect(dir, N).normalized();
    vec3 refract_dir = refract(dir, N, material.refractive_index).normalized();
    vec3 reflect_color = cast_ray(point, reflect_dir, depth + 1);
    vec3 refract_color = cast_ray(point, refract_dir, depth + 1);

    float diffuse_light_intensity = 0, specular_light_intensity = 0;
    for (const vec3 &light : lights) {  
        vec3 light_dir = (light - point).normalized(); //  阴影检测 
        auto [hit, shadow_pt, trashnrm, trashmat] = scene_intersect(point, light_dir);
        if (hit && (shadow_pt-point).norm() < (light-point).norm()) continue; // 如果有物体遮挡,则不考虑该光线阴影
        diffuse_light_intensity  += std::max(0.f, light_dir*N); //  计算漫反射光线  
        //   计算镜面反射光线，并计算高光反射系数 
        specular_light_intensity += std::pow(std::max(0.f, -reflect(-light_dir, N)*dir), material.specular_exponent);
    }
    /**
    最终颜色由四部分组成：
漫反射颜色 × 漫反射强度 × 漫反射权重(albedo[0])
白色高光 × 高光强度 × 高光权重(albedo[1])
反射颜色 × 反射权重(albedo[2])
折射颜色 × 折射权重(albedo[3])
 */
    return material.diffuse_color * diffuse_light_intensity * material.albedo[0] + vec3{1., 1., 1.}*specular_light_intensity * material.albedo[1] + reflect_color*material.albedo[2] + refract_color*material.albedo[3];
}

int main() {
    constexpr int   width  = 1024;
    constexpr int   height = 768;
    constexpr float fov    = 1.05;  // 视角度 1.05 表示 60° 的视角 , 60*3.14/ 180= 1.05 
    std::vector<vec3> framebuffer(width*height);   // 一帧的像素总数
#pragma omp parallel for   //将紧随其后的 for 循环划分为多个线程并行执行 
    for (int pix = 0; pix<width*height; pix++) {  
         // Vec3{0,0,0} 摄像机位置 
        float dir_x =  (pix%width + 0.5) -  width/2.; 
        float dir_y = -(pix/width + 0.5) + height/2.; 
        float dir_z = -height/(2.*tan(fov/2.)); 
        framebuffer[pix] = cast_ray(vec3{0,0,0}, vec3{dir_x, dir_y, dir_z}.normalized());
    }
    // P6 表示 PPM 格式, 255 表示最大颜色值 
    std::ofstream ofs("./out.ppm", std::ios::binary);
    ofs << "P6\n" << width << " " << height << "\n255\n";
    /**
    遍历帧缓冲区的所有像素颜色：
计算当前像素RGB分量的最大值（但不小于1，避免除以零）
将每个颜色通道值归一化到[0,255]范围
以二进制形式写入文件 */
    for (vec3 &color : framebuffer) {
        float max = std::max(1.f, std::max(color[0], std::max(color[1], color[2])));
        for (int chan : {0,1,2})
            ofs << (char)(255 *  color[chan]/max); // 每个像素3个字节
    }
    return 0;
}

